#include "BlockStorage.h"
#include "Signals.h"
#include "Utils.h"

using namespace std;

// start block downloading
/**************************************************************************************************************/
void BlockStorage :: startGettingBlock(int block_id) {
    if(isInLocalMemory(block_id)) {
        getFromLocalMemory(block_id);
    }
    else {
        downloadFromRemote(block_id);
    }
}

bool BlockStorage :: isInLocalMemory(int block_id) {
    return prev_blocks.count(block_id) == 1;
}

void BlockStorage :: getFromLocalMemory(int block_id) {
    Block* block = prev_blocks[block_id];
    current_blocks[block_id] = block;
}

void BlockStorage :: downloadFromRemote(int block_id) {
    Block* new_block = createBlock(block_id);
    int source_location = findBlockLocation(block_id);
    new_block -> startDownloading(source_location);
    downloadsInProgress.push(new_block);
}

int BlockStorage :: findBlockLocation(int block_id) {
    int slave_source;
    MPI_Status status;
    MPI_Send(&block_id, 1, MPI_INT, 0, Signals :: WHERE_IS_BLOCK, MPI_COMM_WORLD);
    MPI_Recv(&slave_source, 1, MPI_INT, 0, Signals :: BLOCK_LOCATION, MPI_COMM_WORLD, &status);
    return slave_source;
}
/**************************************************************************************************************/


// finish downloading / check state of downloading
/**************************************************************************************************************/
void BlockStorage :: waitForBlocksAvailability() {
    while(downloadsInProgress.empty() == false) {
        Block* block = downloadsInProgress.front();
        block -> waitForDownloadCompletion();
        addToStorage(block);
        downloadsInProgress.pop();
    }
}

bool BlockStorage :: areAllBlocksAvailable() {
    while(downloadsInProgress.empty() == false) {
        Block* block = downloadsInProgress.front();
        if(block -> isDownloaded() == false) {
            downloadsInProgress.push(block);
            return false;
        }
        addToStorage(block);
        downloadsInProgress.pop();
    }
    return true;
}

void BlockStorage :: addToStorage(Block* new_block) {
    current_blocks[new_block -> getId()] = new_block;
}
/**************************************************************************************************************/


// swaping old / new blocks. Block deletion
/**************************************************************************************************************/
void BlockStorage :: nextIteration() {
    deleteUnusedBlocks();
    prev_blocks.swap(current_blocks);
    current_blocks.clear();
}

void BlockStorage :: deleteUnusedBlocks() {
    set<Block*> blocks_in_usage;
    setBlocksInUsage(blocks_in_usage);
    deleteOldBlocksExcept(blocks_in_usage);
}

void BlockStorage :: setBlocksInUsage(std :: set<Block*>& in_usage) {
    MapValuesIterator<int, Block*> iterator(&current_blocks);
    while(iterator.hasNext()) {
        in_usage.insert(iterator.next());
    }
}

void BlockStorage :: deleteOldBlocksExcept(std :: set<Block*>& blocks_in_usage) {
    MapValuesIterator<int, Block*> iterator(&prev_blocks);
    while(iterator.hasNext()) {
        Block* next_prev_block = iterator.next();
        if(blocks_in_usage.count(next_prev_block) == 0) {
            next_prev_block -> dispose();
            delete next_prev_block;
        } 
    }
}
/**************************************************************************************************************/


// constructor and destructor
/**************************************************************************************************************/
BlockStorage :: BlockStorage() {
    block_server.setBlockSource(&prev_blocks);
}

BlockStorage :: ~BlockStorage() {
    deleteUnusedBlocks();
    prev_blocks.swap(current_blocks);
    current_blocks.clear();
    deleteUnusedBlocks();
}
/**************************************************************************************************************/


// BlockServer methods
/**************************************************************************************************************/
void BlockStorage :: BlockServer :: listenForRequest() {
    MPI_Irecv(&requested_block_id, 1, MPI_INT, MPI_ANY_SOURCE, Signals :: SEND_ME_BLOCK, MPI_COMM_WORLD, &request);
}

void BlockStorage :: BlockServer :: sendResponse() {
    Block* block = block_source -> find(requested_block_id) -> second;
    block -> sendDataTo(status.MPI_SOURCE);
}
/**************************************************************************************************************/
